/*
 * Project: N|Watch
 * Author: Zak Kemble, contact@zakkemble.co.uk
 * Copyright: (C) 2013 by Zak Kemble
 * License: GNU GPL v3 (see License.txt)
 * Web: http://blog.zakkemble.co.uk/diy-digital-wristwatch/
 */
#include <stdlib.h>
#include <stdio.h>

#include "game1.h"
#include "cmsis_os.h"
#include "FreeRTOS.h"                   // ARM.FreeRTOS::RTOS:Core
#include "task.h"                       // ARM.FreeRTOS::RTOS:Core
#include "queue.h" 
#include "event_groups.h"               // ARM.FreeRTOS::RTOS:Event Groups
#include "semphr.h"                     // ARM.FreeRTOS::RTOS:Core

#include "draw.h"
#include "resources.h"

#include "driver_lcd.h"
#include "driver_ir_receiver.h"
#include "driver_rotary_encoder.h"
#include "driver_mpu6050.h"
#include "music.h"

#define NOINVERT	false
#define INVERT		true

#define sprintf_P  sprintf
#define PSTR(a)  a

#define PLATFORM_WIDTH	12
#define PLATFORM_HEIGHT	4

#define BLOCK_COLS		32
#define BLOCK_ROWS		5
#define BLOCK_COUNT		(BLOCK_COLS * BLOCK_ROWS)

typedef struct{
	float x;
	float y;
	float velX;
	float velY;
}s_ball;

static const byte block[] ={
	0x07,0x07,0x07,
};

static const byte platform[] ={
	0x60,0x70,0x50,0x10,0x30,0xF0,0xF0,0x30,0x10,0x50,0x70,0x60,
};

static const byte ballImg[] ={
	0x03,0x03,
};

static const byte clearImg[] ={
	0,0,0,0,0,0,0,0,0,0,0,0,
};

static bool btnExit(void);
static bool btnRight(void);
static bool btnLeft(void);
void game1_draw(void);

static byte uptMove;
static s_ball ball;
static bool* blocks;
static byte lives, lives_origin;
static uint score;
static byte platformX;

static uint32_t g_xres, g_yres, g_bpp;
static uint8_t *g_framebuffer;

// 挡球板数据消息队列
QueueHandle_t gxQueuePlatformHandle;

// 旋转编码器数据消息队列
//QueueHandle_t gxQueueRotaryHandle;
//static uint8_t gucQueueRotaryBuff[10*sizeof(struct RotaryData)];
//StaticQueue_t gxQueueRotary;


static QueueHandle_t gxQueueRotaryHandle;// 旋转编码器消息队列
static QueueHandle_t gxQueueIrHandle;// 红外消息队列
static QueueHandle_t gxQueueMpuHandle;// MPU消息队列

// 输入总线队列
static QueueSetHandle_t gxQueueSetInputHandle;
void Input_task(void* params);

/* 挡球板任务 */
static void platform_task(void *params)
{
    byte platformXtmp = platformX;    
    uint8_t dev, data, last_data;
    struct InputData_Type Data = {0};

    // Draw platform
    draw_bitmap(platformXtmp, g_yres - 8, platform, 12, 8, NOINVERT, 0);
    draw_flushArea(platformXtmp, g_yres - 8, 12, 8);
    
    while (1)
    {
        // 读取挡球板消息队列
        xQueueReceive(gxQueuePlatformHandle,&Data,portMAX_DELAY);// 可以改成非阻塞，但是挡球板会闪，且需要主动让出CPU
		
        uptMove = Data.Value;
        
        // Hide platform
        draw_bitmap(platformXtmp, g_yres - 8, clearImg, 12, 8, NOINVERT, 0);
        draw_flushArea(platformXtmp, g_yres - 8, 12, 8);
        
        // Move platform
        if(uptMove == UPT_MOVE_RIGHT)
            platformXtmp += 3;
        else if(uptMove == UPT_MOVE_LEFT)
            platformXtmp -= 3;
        uptMove = UPT_MOVE_NONE;
        Data.Value = 0;
        
        // Make sure platform stays on screen
        if(platformXtmp > 250)
            platformXtmp = 0;
        else if(platformXtmp > g_xres - PLATFORM_WIDTH)
            platformXtmp = g_xres - PLATFORM_WIDTH;
        
        // Draw platform
        draw_bitmap(platformXtmp, g_yres - 8, platform, 12, 8, NOINVERT, 0);
        draw_flushArea(platformXtmp, g_yres - 8, 12, 8);
        
        platformX = platformXtmp;
            
//		vTaskDelay(1);
    }
}

// 游戏任务
// ->创建挡板任务
// ->游戏绘制任务
void game1_task(void *params)
{		
	

	buzzer_init();
	
    uint8_t dev, data, last_data;
    // 创建挡球板输入消息队列
    gxQueuePlatformHandle = xQueueCreate(10,sizeof(struct InputData_Type));
//    
//    gxQueueRotaryHandle = xQueueCreateStatic(10,sizeof(struct RotaryData),gucQueueRotaryBuff,&gxQueueRotary);
//    

	// 创建队列集
	gxQueueSetInputHandle = xQueueCreateSet(30);
    
    // 
    gxQueueRotaryHandle = Queue_GetRotaryHandle();
    gxQueueIrHandle = Queue_GetIrHandle();
	gxQueueMpuHandle = Queue_GetMpuHandle();
    
    // 添加句柄到队列集
    xQueueAddToSet(gxQueueRotaryHandle,gxQueueSetInputHandle);
    xQueueAddToSet(gxQueueIrHandle,gxQueueSetInputHandle);
	xQueueAddToSet(gxQueueMpuHandle,gxQueueSetInputHandle);
    
    // 创建输入任务
    xTaskCreate(Input_task, "Input_task", 128, NULL, osPriorityNormal, NULL);
//    xTaskCreate(MPU6050_Task, "MpuTask", 128, NULL, osPriorityNormal, NULL);
    g_framebuffer = LCD_GetFrameBuffer(&g_xres, &g_yres, &g_bpp);
    draw_init();
    draw_end();
    
	uptMove = UPT_MOVE_NONE;

	ball.x = g_xres / 2;
	ball.y = g_yres - 10;
        
	ball.velX = -0.5;
	ball.velY = -0.6;
//	ball.velX = -1;
//	ball.velY = -1.1;
    
    
	blocks = pvPortMalloc(BLOCK_COUNT);
    memset(blocks, 0, BLOCK_COUNT);
	
	lives = lives_origin = 3;
	score = 0;
	platformX = (g_xres / 2) - (PLATFORM_WIDTH / 2);

    xTaskCreate(platform_task, "platform_task", 128, NULL, osPriorityNormal, NULL);

    while (1)
    {
        game1_draw();
        //draw_end();
        vTaskDelay(50);
    }
}

static bool btnExit()
{
	
	vPortFree(blocks);
	if(lives == 255)
	{
		//game1_start();
	}
	else
	{
		//pwrmgr_setState(PWR_ACTIVE_DISPLAY, PWR_STATE_NONE);	
		//animation_start(display_load, ANIM_MOVE_OFF);
		vTaskDelete(NULL);
	}
	return true;
}

static bool btnRight()
{
	uptMove = UPT_MOVE_RIGHT;
	return false;
}

static bool btnLeft()
{
	uptMove = UPT_MOVE_LEFT;
	return false;
}


// 游戏绘制
void game1_draw()
{
	bool gameEnded = ((score >= BLOCK_COUNT) || (lives == 255));

	byte platformXtmp = platformX;

    static bool first = 1;

	// Move ball 移动球
	// hide ball 隐藏球
	draw_bitmap(ball.x, ball.y, clearImg, 2, 2, NOINVERT, 0);
    draw_flushArea(ball.x, ball.y, 2, 8);

    // Draw platform
    //draw_bitmap(platformX, g_yres - 8, platform, 12, 8, NOINVERT, 0);
    //draw_flushArea(platformX, g_yres - 8, 12, 8);
	
    // 如果有触碰 则改变球方向
	if(!gameEnded)
	{
		ball.x += ball.velX;
		ball.y += ball.velY;
	}

	bool blockCollide = false;
	const float ballX = ball.x;
	const byte ballY = ball.y;

	// Block collision
	byte idx = 0;
	LOOP(BLOCK_COLS, x)
	{
		LOOP(BLOCK_ROWS, y)
		{
			if(!blocks[idx] && ballX >= x * 4 && ballX < (x * 4) + 4 && ballY >= (y * 4) + 8 && ballY < (y * 4) + 8 + 4)
			{
//				buzzer_buzz(100, TONE_2KHZ, VOL_UI, PRIO_UI, NULL);
				buzzer_buzz(100,2000);
				// led_flash(LED_GREEN, 50, 255); // 100ask todo
				blocks[idx] = true;

                // hide block
                draw_bitmap(x * 4, (y * 4) + 8, clearImg, 3, 8, NOINVERT, 0);                
                draw_flushArea(x * 4, (y * 4) + 8, 3, 8);                
				blockCollide = true;
				score++;
			}
			idx++;
		}
	}


	// Side wall collision
	if(ballX > g_xres - 2)
	{
		if(ballX > 240)
			ball.x = 0;		
		else
			ball.x = g_xres - 2;
		ball.velX = -ball.velX;		
	}
	if(ballX < 0)
  {
		ball.x = 0;		
		ball.velX = -ball.velX;	
  }

	// Platform collision
	bool platformCollision = false;
	if(!gameEnded && ballY >= g_yres - PLATFORM_HEIGHT - 2 && ballY < 240 && ballX >= platformX && ballX <= platformX + PLATFORM_WIDTH)
	{
		platformCollision = true;
		// buzzer_buzz(200, TONE_5KHZ, VOL_UI, PRIO_UI, NULL); // 100ask todo
		buzzer_buzz(200,5000);
		ball.y = g_yres - PLATFORM_HEIGHT - 2;
		if(ball.velY > 0)
			ball.velY = -ball.velY;
		ball.velX = ((float)rand() / (RAND_MAX / 2)) - 1; // -1.0 to 1.0
	}

	// Top/bottom wall collision
	if(!gameEnded && !platformCollision && (ballY > g_yres - 2 || blockCollide))
	{
		if(ballY > 240)
		{
			// buzzer_buzz(200, TONE_2_5KHZ, VOL_UI, PRIO_UI, NULL); // 100ask todo
			buzzer_buzz(200,2500);
			ball.y = 0;
		}
		else if(!blockCollide)
		{
			// buzzer_buzz(200, TONE_2KHZ, VOL_UI, PRIO_UI, NULL); // 100ask todo
			buzzer_buzz(200,2000);
			ball.y = g_yres - 1;
			lives--;
		}
		ball.velY *= -1;
	}

	// Draw ball
	draw_bitmap(ball.x, ball.y, ballImg, 2, 2, NOINVERT, 0);
    draw_flushArea(ball.x, ball.y, 2, 8);

    // Draw platform
    //draw_bitmap(platformX, g_yres - 8, platform, 12, 8, NOINVERT, 0);
    //draw_flushArea(platformX, g_yres - 8, 12, 8);

    if (first)
    {
        first = 0;
        
    	// Draw blocks
    	idx = 0;
    	LOOP(BLOCK_COLS, x)
    	{
    		LOOP(BLOCK_ROWS, y)
    		{
    			if(!blocks[idx])
    			{
    				draw_bitmap(x * 4, (y * 4) + 8, block, 3, 8, NOINVERT, 0);
                    draw_flushArea(x * 4, (y * 4) + 8, 3, 8);                
    			}
    			idx++;
    		}
    	}
        
    }

	// Draw score
	char buff[6];
	sprintf_P(buff, PSTR("%u"), score);
	draw_string(buff, false, 0, 0);

    // Draw lives
    if(lives != 255)
    {
        LOOP(lives_origin, i)
        {
            if (i < lives)
                draw_bitmap((g_xres - (3*8)) + (8*i), 1, livesImg, 7, 8, NOINVERT, 0);
            else
                draw_bitmap((g_xres - (3*8)) + (8*i), 1, clearImg, 7, 8, NOINVERT, 0);
            draw_flushArea((g_xres - (3*8)) + (8*i), 1, 7, 8);    
        }
    }   

	// Got all blocks
	if(score >= BLOCK_COUNT)
		draw_string_P(PSTR(STR_WIN), false, 50, 32);

	// No lives left (255 because overflow)
	if(lives == 255)
		draw_string_P(PSTR(STR_GAMEOVER), false, 34, 32);

}

// 输入总线任务，应用层处理数据
static void Input_task(void* params)
{

    struct IrData_Type iData;
    struct RotaryData_Type rData;
	struct MPU6050_Type MpuData;
    struct InputData_Type InputData;
    QueueSetMemberHandle_t xQueueSetHandle;
    uint8_t i,count;
    while(1)
    {
        // 读队列集
        xQueueSetHandle = xQueueSelectFromSet(gxQueueSetInputHandle,portMAX_DELAY);

		// 利用队列句柄，判断队列类型
		// 旋转编码器数据处理
        if(xQueueSetHandle == gxQueueRotaryHandle)
        {
            xQueueReceive(gxQueueRotaryHandle,&rData,0);
            if(rData.Speed < 0)
            {
                InputData.Value = UPT_MOVE_LEFT;
                rData.Speed = 0 -rData.Speed;  
            }
            else
            {
                InputData.Value = UPT_MOVE_RIGHT;
            }
            
            InputData.Dev = 1;
            
            if(rData.Speed > 100)
                count = 4;
            else if(rData.Speed > 50)
                count = 2;
            else 
                count = 1;
            
                
            for(i = 0;i<count; i++)
            {
                xQueueSend(gxQueuePlatformHandle,&InputData,0);
            }
            
        }
        
        // 红外数据处理
        else if(xQueueSetHandle == gxQueueIrHandle)
        {
            xQueueReceive(gxQueueIrHandle,&iData,0);
            if(iData.Value == IR_KEY_LEFT)
            {
                InputData.Dev = iData.Dev;
                InputData.Value = UPT_MOVE_LEFT;
            }
            else if(iData.Value == IR_KEY_RIGHT)
            {
                InputData.Dev = iData.Dev;
                InputData.Value = UPT_MOVE_RIGHT;
            }
            else if(iData.Value == IR_KEY_REPEAT)
            {   
            // 不改动，就是重复
            }
            else
            {
                InputData.Dev = iData.Dev;
                InputData.Value = UPT_MOVE_NONE;
            }
            xQueueSend(gxQueuePlatformHandle,&InputData,0);
        }
		
		else if(xQueueSetHandle == gxQueueMpuHandle)
        {
            xQueueReceive(gxQueueMpuHandle,&MpuData,0);
			
			if(MpuData.Angle_X > 92)
				InputData.Value = UPT_MOVE_LEFT;
			else if(MpuData.Angle_X < 88)
				InputData.Value = UPT_MOVE_RIGHT;
			else
				InputData.Value = UPT_MOVE_NONE;
				
			InputData.Dev = 1;

            xQueueSend(gxQueuePlatformHandle,&InputData,0);
        }

   
    }
    
    
}
